﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Sora.HelpSystem.Package;
using mshtml;
using Sora.HelpSystem.Package.Pages;

namespace Sora.HelpSystem.UI.WPF
{
    /// <summary>
    /// Interaction logic for HelpContentDisplay.xaml
    /// </summary>
    public partial class HelpContentDisplay : UserControl
    {
        private HelpDocumentProvider DocumentProvider { get; set; }

        #region Dependency Properties

        public HelpDocument Document
        {
            get { return (HelpDocument)GetValue(DocumentProperty); }
            set { SetValue(DocumentProperty, value); }
        }

        public static readonly DependencyProperty DocumentProperty =
            DependencyProperty.Register("Document", typeof(HelpDocument), typeof(HelpContentDisplay), new PropertyMetadata(DocumentPropertyChanged));

        public Uri CurrentPageUri
        {
            get { return (Uri)GetValue(CurrentPageUriProperty); }
            set { SetValue(CurrentPageUriProperty, value); }
        }

        public static readonly DependencyProperty CurrentPageUriProperty =
            DependencyProperty.Register("CurrentPageUri", typeof(Uri), typeof(HelpContentDisplay), new PropertyMetadata(CurrentPageUriPropertyChanged));

        public string CurrentPageTitle
        {
            get { return (string)GetValue(CurrentPageTitleProperty.DependencyProperty); }
            private set { SetValue(CurrentPageTitleProperty, value); }
        }

        public static readonly DependencyPropertyKey CurrentPageTitleProperty =
            DependencyProperty.RegisterReadOnly("CurrentPageTitle", typeof(string), typeof(HelpContentDisplay), new PropertyMetadata(""));

        public HelpPage CurrentHelpPage
        {
            get { return (HelpPage)GetValue(CurrentHelpPageProperty.DependencyProperty); }
            private set { SetValue(CurrentHelpPageProperty, value); }
        }

        public static readonly DependencyPropertyKey CurrentHelpPageProperty =
            DependencyProperty.RegisterReadOnly("CurrentHelpPage", typeof(HelpPage), typeof(HelpContentDisplay), new PropertyMetadata(null));

        public bool CanNavigateFromHyperlink
        {
            get { return (bool)GetValue(CanNavigateFromHyperlinkProperty); }
            private set { SetValue(CanNavigateFromHyperlinkProperty, value); }
        }

        public static readonly DependencyProperty CanNavigateFromHyperlinkProperty =
            DependencyProperty.Register("CanNavigateFromHyperlink", typeof(bool), typeof(HelpContentDisplay), new PropertyMetadata(true));

        #endregion

        #region Property Changed Events

        private static void DocumentPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var contentDisplay = sender as HelpContentDisplay;

            if (contentDisplay != null)
            {
                if (e.NewValue == null)
                {
                    contentDisplay.DocumentProvider = null;
                }
                else
                {
                    var doc = (HelpDocument)e.NewValue;

                    contentDisplay.DocumentProvider = new HelpDocumentProvider(doc);
                }

                /* Loading a new document invalidate history */
                contentDisplay.HistoryNext.Clear();
                contentDisplay.HistoryPrevious.Clear();

                var oldUri = contentDisplay.CurrentPageUri;
                contentDisplay.SetCurrentPageUri(null);
                contentDisplay.SetCurrentPageUri(oldUri);
            }
        }

        private bool CurrentPageUriEventEnabled { get; set; }

        private static void CurrentPageUriPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var contentDisplay = sender as HelpContentDisplay;

            if (contentDisplay != null)
            {
                contentDisplay.CancelRequested = false;

                if (contentDisplay.DocumentProvider != null)
                {
                    if (contentDisplay.CurrentPageUriEventEnabled)
                    {
                        var uri = (Uri)e.NewValue;

                        if (uri == null)
                        {
                            contentDisplay.externalHelpContent.Visibility = Visibility.Hidden;
                            contentDisplay.helpContent.Visibility = Visibility.Hidden;
                        }
                        else
                        {
                            if (contentDisplay.DocumentProvider.IsHelpUri(uri))
                            {
                                if (e.OldValue != e.NewValue)
                                    contentDisplay.HistoryMark(e.OldValue as Uri);

                                var page = contentDisplay.DocumentProvider.ResolveUri(uri);

                                contentDisplay.CurrentHelpPage = page;

                                if (page != null)
                                    contentDisplay.ActualPageUri = uri;

                                var oldDocument = contentDisplay.helpContent.Document;

                                if (oldDocument != null)
                                    oldDocument.RemoveHandler(FlowDocument.PreviewMouseDownEvent, (RoutedEventHandler)contentDisplay.HyperlinkNavigateEvent);

                                try
                                {
                                    contentDisplay.helpContent.Document = page != null ? page.FlowContent.Source : null;
                                }
                                catch (ArgumentException)
                                {
                                    /* can happend if we try to specify a content that was already used for another
                                     * instance of HelpContentDisplay. We create a new document host
                                     */
                                    contentDisplay.helpContent.Document = page != null ? page.FlowContent.GetCopy() : null;
                                }

                                contentDisplay.helpContent.Document.AddHandler(FlowDocument.PreviewMouseDownEvent, (RoutedEventHandler)contentDisplay.HyperlinkNavigateEvent, true);

                                contentDisplay.CurrentPageTitle = page != null ? page.SafePageTitle : String.Empty;

                                contentDisplay.helpContent.Visibility = Visibility.Visible;
                                contentDisplay.externalHelpContent.Visibility = Visibility.Hidden;
                            }
                            else
                            {
                                contentDisplay.CurrentHelpPage = null;

                                contentDisplay.externalHelpContent.Navigate(uri);

                                contentDisplay.externalHelpContent.Visibility = Visibility.Visible;
                                contentDisplay.helpContent.Visibility = Visibility.Hidden;
                            }
                        }
                    }
                }
                else
                {
                    contentDisplay.externalHelpContent.Visibility = Visibility.Hidden;
                    contentDisplay.helpContent.Visibility = Visibility.Hidden;
                }
            }
        }

        private void HyperlinkNavigateEvent(object sender, RoutedEventArgs e)
        {
            if (CanNavigateFromHyperlink)
            {
                var element = e.Source as FrameworkContentElement;

                while (element != null)
                {
                    if (element.Parent is Hyperlink)
                    {
                        var link = (Hyperlink)element.Parent;

                        if (link.NavigateUri.IsAbsoluteUri)
                            SetCurrentPageUri(link.NavigateUri);
                        else
                            SetCurrentPageUri(new Uri(Document.DocumentBaseUri.AbsoluteUri + "/" + link.NavigateUri.ToString()));

                        break;
                    }
                    else
                        element = element.Parent as FrameworkContentElement;
                }
            }
        }

        #endregion

        #region History

        private Stack<Uri> HistoryPrevious { get; set; }
        private Stack<Uri> HistoryNext { get; set; }

        private bool HistoryEnabled { get; set; }

        public void HistoryGoBack()
        {
            if (HistoryPrevious.Count > 0)
            {
                HistoryEnabled = false;
                try
                {
                    HistoryNext.Push(ActualPageUri);
                    SetCurrentPageUri(ActualPageUri = HistoryPrevious.Pop());
                }
                finally
                {
                    HistoryEnabled = true;
                }
            }
        }

        public void HistoryGoNext()
        {
            if (HistoryNext.Count > 0)
            {
                HistoryEnabled = false;
                try
                {
                    HistoryPrevious.Push(ActualPageUri);
                    SetCurrentPageUri(ActualPageUri = HistoryNext.Pop());
                }
                finally
                {
                    HistoryEnabled = true;
                }
            }
        }

        private void HistoryMark(Uri uri)
        {
            if (HistoryEnabled)
            {
                if (uri != null && !String.IsNullOrEmpty(uri.AbsoluteUri))
                {
                    if (HistoryPrevious.Count > 0)
                    {
                        if (HistoryPrevious.Peek() == uri)
                            return;
                    }

                    HistoryPrevious.Push(uri);
                    HistoryNext.Clear();
                }
            }
        }

        #endregion

        private Uri ActualPageUri { get; set; }

        public HelpContentDisplay()
        {
            ActualPageUri = null;

            HistoryPrevious = new Stack<Uri>();
            HistoryNext = new Stack<Uri>();
            HistoryEnabled = true;
            Navigating = false;

            CurrentPageUriEventEnabled = true;

            InitializeComponent();

            CancelRequested = false;
        }

        private void SetCurrentPageUri(Uri v)
        {
            var currentPageBinding = BindingOperations.GetBindingBase(this, CurrentPageUriProperty);

            CurrentPageUri = v;

            if (currentPageBinding != null)
                BindingOperations.SetBinding(this, CurrentPageUriProperty, currentPageBinding);

            CoerceValue(CurrentPageUriProperty);
        }

        private bool CancelRequested { get; set; }

        private bool Navigating { get; set; }

        private void externalHelpContent_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            Navigating = true;

            if (CancelRequested)
            {
                e.Cancel = true;
                CancelRequested = false;
                Navigating = false;
            }
        }

        private void externalHelpContent_Navigated(object sender, NavigationEventArgs e)
        {
            Navigating = false;
            CurrentPageUriEventEnabled = false;
            try
            {
                if (ActualPageUri != e.Uri)
                    HistoryMark(ActualPageUri);

                ActualPageUri = e.Uri;
                SetCurrentPageUri(e.Uri);

                var hDocument = externalHelpContent.Document as IHTMLDocument2;

                CurrentPageTitle = hDocument != null ? hDocument.title : e.Uri.AbsoluteUri;
            }
            finally
            {
                CurrentPageUriEventEnabled = true;
            }
        }

        public void Stop()
        {
            if (Navigating)
                if (!CancelRequested)
                    CancelRequested = true;
        }
    }
}
